package gao.xiaolei.service;

import java.nio.charset.StandardCharsets;
import java.time.Clock;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import javax.annotation.Resource;

import gao.xiaolei.dao.*;
import gao.xiaolei.exception.AuthException;
import gao.xiaolei.exception.StatusException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;
import gao.xiaolei.dto.TestRequirementDesignCoverDto;
import gao.xiaolei.dto.TestRequirementDto;
import gao.xiaolei.entity.TestDesign;
import gao.xiaolei.entity.TestRequirement;
import gao.xiaolei.entity.TestRequirementDesignCover;
import gao.xiaolei.entity.TestStep;
import xiaolei.gao.file.FileInterface;
import gao.xiaolei.util.ServiceName;
import gao.xiaolei.util.UuidUtil;

@Service("TestRequirementService")
public class TestRequirementService {

    @Resource(name = "TestRequirementDao")
    private TestRequirementDao testRequirementDao;

    @Resource(name = "TestStatusDao")
    private TestStatusDao testStatusDao;

    @Resource(name = "TestRequirementCheckDao")
    private TestRequirementCheckDao testRequirementCheckDao;

    @Autowired
    private RestTemplate restTemplate;

    @Resource(name = "FileInterface")
    private FileInterface fileInterface;

    @Resource(name = "TestRequirementDesignCoverDao")
    private TestRequirementDesignCoverDao testRequirementDesignCoverDao;

    @Resource(name = "TestDesignDao")
    private TestDesignDao testDesignDao;

    @Resource(name = "TestStepDao")
    private TestStepDao testStepDao;

    @Resource(name = "AnalysisService")
    private AnalysisService analysisService;

    // 添加测试需求
    @Transactional
    public void addTestRequirement(String id, String title, String author, int status, String parentId, String project, int leaf) {
        LocalDateTime createDate = LocalDateTime.now(Clock.system(ZoneId.of("Asia/Shanghai")));
        if ("root".equals(parentId))//如果添加的是最顶端的测试需求就直接添加就好了
            testRequirementDao.save(new TestRequirement(id, title, author, status, parentId, project, createDate, createDate, leaf));
        else {//如果添加进已有的树的话
            Integer parentStatus = testRequirementDao.getStatus(parentId);
            if (parentStatus == 2)//这个这颗树本身就是审核中的话
                throw new StatusException("审核中的节点无法修改");
            else {//如果这颗树可以添加的话
                if (testRequirementDao.getChildrenCount(parentId) == 0)//如果新增节点不是最顶节点并且父节点本身即是叶子节点就要把父节点先变成非叶子节点
                    testRequirementDao.updateLeaf(parentId, 0);
                testRequirementDao.save(new TestRequirement(id, title, author, status, parentId, project, createDate, createDate, leaf));
                updateStatusAll(id, status);//整棵树的状态根据新节点的状态来修改
            }
        }
    }


    // 删除测试需求
    @Transactional
    public void deleteTestRequirement(String id, String userId) {
        if (!testRequirementDao.getAuthor(id).orElse("").equals(userId))
            throw new AuthException("你无此权限删除！");
        if (testRequirementDao.getStatus(id) == 2)
            throw new StatusException("审核中的节点不能删除");
        String projectId = testRequirementDao.getProject(id);
        String parentId = testRequirementDao.getParentId(id);
        updateStatusAll(id, 0);//整个树都变成已完成
        List<String> willDeleteFile = new LinkedList<>();
        recurrenceDelete(id, willDeleteFile);//递归删除其所有的子节点，包括其自己
        // 判断如果删除这个节点之后会不会父节点会不会没有儿子节点，如果没有就将父节点的leaf设为1
        if (testRequirementDao.getChildrenCount(parentId) == 0)
            testRequirementDao.updateLeaf(parentId, 1);
        analysisService.doAnalysis(projectId);//重新生成需求分析
        for (String wd : willDeleteFile) {
            fileInterface.fileDelete(id, projectId);
        }
    }


    //修改测试需求
    @Transactional
    public void updateTestRequirement(String id, String title, int status, String userId) {
        if (!testRequirementDao.getAuthor(id).orElse("").equals(userId))
            throw new AuthException("你无此权限修改！");
        if (testRequirementDao.getStatus(id) == 2)
            throw new StatusException("审核中的节点不能修改");
        LocalDateTime updateDate = LocalDateTime.now(Clock.system(ZoneId.of("Asia/Shanghai")));
        testRequirementDao.updateForEditor(id, title, status, updateDate);
        updateStatusAll(id, status);//将此节点所处整棵树的状态都置的和该节点一样
        analysisService.doAnalysis(testRequirementDao.getProject(id));//重新生成需求分析
    }

    //修改测试需求标题
    @Transactional
    public void updateTestRequirementTitle(String id, String title, String userId) {
        if (!testRequirementDao.getAuthor(id).orElse("").equals(userId))
            throw new AuthException("你无此权限修改！");
        if (testRequirementDao.getStatus(id) == 2)
            throw new StatusException("审核中的节点不能修改");
        testRequirementDao.updateTitle(id, title);
        updateStatusAll(id, 0);//将此节点所处整棵树的状态都置为未完成
        analysisService.doAnalysis(testRequirementDao.getProject(id));//重新生成需求分析
    }

    // 修改测试需求的父测试需求
    @Transactional
    public void updateParentId(String id, String newParentId, String userId) {
        if (!testRequirementDao.getAuthor(id).orElse("").equals(userId))
            throw new AuthException("你无此权限移动！");
        if (testRequirementDao.getStatus(id) == 2 || testRequirementDao.getStatus(newParentId) == 2)
            throw new StatusException("审核中的节点不能移出或移入！");
        // 判断如果移走这个节点之后会不会原父节点会不会没有儿子节点，如果没有就将父节点的leaf设为1
        String oldParentId = testRequirementDao.getParentId(id);
        if (testRequirementDao.getChildrenCount(oldParentId) <= 1)
            testRequirementDao.updateLeaf(oldParentId, 1);
        //判断新父亲节点是不是叶子节点，如果是则将其先变成不是叶子节点
        if (testRequirementDao.getLeaf(newParentId) == 1)
            testRequirementDao.updateLeaf(newParentId, 0);
        testRequirementDao.updateParentId(id, newParentId);
        updateStatusAll(newParentId, 0);//将新父亲所在的树都改为已完成
        if ("root".equals(oldParentId))
            updateStatusAll(oldParentId, 0);//将旧父亲所在的树都改为已完成
        analysisService.doAnalysis(testRequirementDao.getProject(id));//重新生成需求分析
    }

    //根据项目和作者和是否已经通过审核来查询所有最顶端需求(一个人只能看自己写的和别人已经审核通过了的)
    @Transactional(readOnly = true)
    public List<TestRequirement> findByProject(String projectId, String userId) {
        List<Object[]> os = testRequirementDao.findByProject(projectId, userId);
        return objToTestRequirement(os);
    }

    // 获取该需求的子需求列表
    @Transactional(readOnly = true)
    public List<TestRequirement> getChildren(String parentId) {
        List<Object[]> os = testRequirementDao.querySomeByParentId(parentId);
        return objToTestRequirement(os);
    }

    // 获取该需求的详细信息
    @Transactional(readOnly = true)
    public TestRequirementDto getDetail(String id) {
        TestRequirement req = testRequirementDao.findById(id).orElseThrow(() -> new IllegalArgumentException("该需求不存在！"));
        String author = restTemplate.getForObject(ServiceName.AUTH_SERVICE.getServiceName().concat("/user/realName/").concat(req.getAuthor()), String.class);
        String status = testStatusDao.getDescribeOfCode(req.getStatus());
        Integer count = testRequirementCheckDao.getCountByTargetId(id);
        return new TestRequirementDto(id, req.getTitle(), author, req.getAuthor(), status, req.getCreateDate(), req.getUpdateDate(), count);
    }

    //获取该测试项目下通过审核的最顶端测试需求
    @Transactional(readOnly = true)
    public List<TestRequirement> getByProjectIsPass(String projectId) {
        return objToTestRequirement4(testRequirementDao.findByProjectAndIsPass(projectId));
    }

    //获取测试需求的详细信息（供测试计划的关联测试需求使用）
    @Transactional(readOnly = true)
    public TestRequirementDesignCoverDto getDetailForTestDesign(String id) {
        TestRequirement req = testRequirementDao.findById(id).orElseThrow(() -> new IllegalArgumentException("该需求不存在！"));
        String reqAuthor = restTemplate.getForObject(ServiceName.AUTH_SERVICE.getServiceName().concat("/user/realName/").concat(req.getAuthor()), String.class);
        byte[] textByte = fileInterface.getFile(req.getId(), req.getProject());
        return new TestRequirementDesignCoverDto(req.getId(), getTrqPath(id).concat(req.getTitle()), reqAuthor, new String(textByte, StandardCharsets.UTF_8));
    }

    //将对应的测试测试需求转化成测试计划，可以选择要不要将子节点转成测试步骤
    @Transactional
    public void transformTrqToDesign(String trqId, String name, int isIncludeChildren, String subject, int priority, String author) {
        TestRequirement trq = testRequirementDao.findById(trqId).orElseThrow(() -> new IllegalArgumentException("该需求不存在！"));
        if (trq.getStatus() != 3)
            throw new StatusException("只能转换通过审核的节点");
        String designId = UuidUtil.get32Uuid();
        LocalDateTime createDate = LocalDateTime.now(Clock.system(ZoneId.of("Asia/Shanghai")));
        String describe = "由测试需求(".concat(trq.getTitle()).concat("）转换而来的测试计划");
        testDesignDao.save(new TestDesign(designId, name, author, describe, trq.getProject(), 0, priority, 1, subject, createDate, createDate));
        testRequirementDesignCoverDao.save(new TestRequirementDesignCover(UuidUtil.get32Uuid(), trqId, designId));
        if (isIncludeChildren == 1) {//如果选择要将子节点转换成测试步骤的话
            List<String> children = testRequirementDao.getChildrenIdByStatus(trqId, 3);
            String[] stepIds = new String[children.size()];
            for (int i = 0, length = children.size(); i < length; i++) {
                stepIds[i] = UuidUtil.get32Uuid();
            }
            for (int i = 0, length = children.size(); i < length; i++) {
                String frontNode = null, backNode = null;
                if (i == 0) {//如果是第一个节点
                    frontNode = "begin";//赋予前缀节点为begin
                    if (i != length - 1)//如果同时还不是最后一个节点
                        backNode = stepIds[i + 1];//将后缀节点指向下一个节点
                }
                if (i == length - 1) {//如果是最后一个阶段
                    backNode = "end";//赋予后缀节点为end
                    if (i != 0)//如果同时还不是第一个节点
                        frontNode = stepIds[i - 1];//将前缀节点指向上一个节点
                }
                if (i != 0 && i != length - 1) {//如果即不是第一个也不是最后一个节点
                    frontNode = stepIds[i - 1];//将前缀节点指向上一个节点
                    backNode = stepIds[i + 1];//将后缀节点指向下一个节点
                }
                testStepDao.save(new TestStep(stepIds[i], designId, testRequirementDao.getTitle(children.get(i)), frontNode, backNode, "无"));
                StringBuilder stepDescribe = new StringBuilder();
                stepDescribe = recurrenceReqTpStepDescribe(children.get(i), trq.getProject(), 0, stepDescribe);//将节点及其通过审核的子节点的内容转化成测试步骤的内容
                fileInterface.fileUpload(stepDescribe.toString(), stepIds[i], trq.getProject());
            }
        }
    }

    //获取对应测试需求的标题
    public String getTitle(String id) {
        return testRequirementDao.getTitle(id);
    }


    //**********************************树操作begin**************************************************//
    //用于递归删除子测试需求的方法
    private void recurrenceDelete(String id, List<String> willDeleteFile) {
        if (testRequirementDao.getLeaf(id) == 0) {//如果不是叶子节点则递归删除
            List<String> cls = testRequirementDao.getChildrenId(id);
            for (int i = 0, length = cls.size(); i < length; i++) {
                recurrenceDelete(cls.get(i), willDeleteFile);
            }
        }
        testRequirementDao.deleteById(id);//最后一定要把自己删除
        testRequirementDesignCoverDao.deleteByTestRequirementId(id);//删除测试需求覆盖
        willDeleteFile.add(id);
    }

    //获取该节点的最顶端节点
    private String getRootParent(String id) {
        String result = id;
        String parentId = testRequirementDao.getParentId(id);
        while (!"root".equals(parentId) && parentId != null) {
            result = parentId;
            parentId = testRequirementDao.getParentId(parentId);
        }
        return result;
    }

    //更改该节点所处的整棵树的状态
    private void updateStatusAll(String id, Integer status) {
        String rootParentId = getRootParent(id);
        recurrenceUpdateStatus(rootParentId, status);
    }

    //递归整棵树并将状态都设置为对应状态
    private void recurrenceUpdateStatus(String id, Integer status) {
        if (testRequirementDao.getLeaf(id) != 1) {//如果不是叶子节点就继续递归
            List<String> children = testRequirementDao.getChildrenId(id);
            for (int i = 0, length = children.size(); i < length; i++) {
                recurrenceUpdateStatus(children.get(i), status);
            }
        }
        testRequirementDao.updateStatus(id, status);
    }

    //递归某个节点将他的测试需求内容已经他的子节点的测试需求内容组装成测试步骤内容
    private StringBuilder recurrenceReqTpStepDescribe(String id, String projectId, int depth, StringBuilder result) {
        StringBuilder whiteSpace = new StringBuilder(5 * depth * 5);//储存缩进的空格
        for (int i = 0; i < depth * 5; i++) {//递归的越深，缩进的越多
            whiteSpace.append("&nbsp");
        }
        String title = testRequirementDao.getTitle(id);//获取节点的标题来作为开始符（<title>）
        result.append(whiteSpace).append("<strong><").append(title).append("-begin></strong><br>");
        byte[] fileByte = fileInterface.getFile(id, projectId);//拿此节点的内容
        String treqDescribe = new String(fileByte, StandardCharsets.UTF_8);
        result.append(treqDescribe).append("<br>");//插入此节点的内容
        if (testRequirementDao.getLeaf(id) == 0) {//如果这个节点有孩子的话
            List<String> children = testRequirementDao.getChildrenIdByStatus(id, 3);
            for (int i = 0, length = children.size(); i < length; i++) {//递归重复上述操作
                result = recurrenceReqTpStepDescribe(children.get(i), projectId, depth + 1, result);
            }
        }
        result.append(whiteSpace).append("<strong><").append(title).append("-end></strong><br>");//在最后加上结束符</title>
        return result;
    }

    //获取某一个测试需求的路径但不包括本身
    private String getTrqPath(String trqId) {
        List<String> path = new LinkedList<>();
        String parentId = testRequirementDao.getParentId(trqId);
        while (!"root".equals(parentId) && parentId != null) {
            path.add(testRequirementDao.getTitle(parentId));
            parentId = testRequirementDao.getParentId(parentId);
        }
        StringBuilder result = new StringBuilder();
        for (int i = path.size() - 1; i >= 0; i--) {
            result.append(path.get(i)).append("/");
        }
        return result.toString();
    }
    //**********************************树操作end**************************************************//

    //**********************************数据库字段转换begin**************************************************//
    //用于将从数据库里查出来的List<Object[]>转换为List<Requirement>
    private List<TestRequirement> objToTestRequirement(List<Object[]> os) {
        List<TestRequirement> ts = new ArrayList<>(os.size());
        for (int i = 0, length = os.size(); i < length; i++) {
            ts.add(new TestRequirement((String) os.get(i)[0], (String) os.get(i)[1], (String) os.get(i)[2], (Integer) os.get(i)[3], (Integer) os.get(i)[4], (String) os.get(i)[5]));
        }
        return ts;
    }

    //用于将从数据库里查出来的List<Object[]>转换为List<Requirement>(id,title,parentId,leaf)
    private List<TestRequirement> objToTestRequirement4(List<Object[]> os) {
        List<TestRequirement> ts = new ArrayList<>(os.size());
        for (int i = 0, length = os.size(); i < length; i++) {
            ts.add(new TestRequirement((String) os.get(i)[0], (String) os.get(i)[1], (String) os.get(i)[2], (Integer) os.get(i)[3]));
        }
        return ts;
    }
    //**********************************数据库字段转换end**************************************************//
}
